﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EuroManager.Domain.Simulator.AI;
using EuroManager.Domain.Simulator.AI.States;
using EuroManager.Domain.Simulator.Engine;
using EuroManager.Domain.Simulator.Helpers;
using EuroManager.Domain.Simulator.Tests.World;
using EuroManager.Domain.Simulator.World;
using NUnit.Framework;
using Rhino.Mocks;

namespace EuroManager.Domain.Simulator.Tests.AI.States
{
    [TestFixture]
    public class ControlBallStateTests
    {
        private ControlBallState state;
        private IPlayer player;
        private ITeam team;
        private IRandomizer random;
        
        [SetUp]
        public void SetUp()
        {
            team = MockRepository.GenerateMock<ITeam>();
            player = MockRepository.GenerateMock<IPlayer>();
            random = MockRepository.GenerateMock<IRandomizer>();
            state = new ControlBallState(player, random);

            team.Stub(t => t.Players).Return(TestHelper.Enumerable(player));
        }

        [Test]
        public void ShouldObserveWhenNotHasBall()
        {
            player.Stub(p => p.HasBall).Return(false);
            player.Expect(p => p.Observe());

            state.Execute();

            player.VerifyAllExpectations();
        }

        [Test]
        public void ShouldChaseBallWhenCannotReachIt()
        {
            player.Stub(p => p.HasBall).Return(true);
            player.Stub(p => p.CanReachBall).Return(false);
            player.Expect(p => p.ChaseBall());

            state.Execute();

            player.VerifyAllExpectations();
        }

        [Test]
        public void ShouldPushBallWhenReachedIt()
        {
            random.Stub(r => r.ShouldPassBall(Arg<float>.Is.Anything)).Return(false);
            player.Stub(p => p.VisiblePlayersFromOwnTeam).Return(Enumerable.Empty<IPlayer>());
            player.Stub(p => p.HasBall).Return(true);
            player.Stub(p => p.CanReachBall).Return(true);
            player.Expect(p => p.PushBallForward());

            state.Execute();

            player.VerifyAllExpectations();
        }

        [Test]
        public void ShouldPassBallWhenReachedIt()
        {
            IPlayer other = MockRepository.GenerateStub<IPlayer>();

            random.Stub(r => r.ShouldPassBall(Arg<float>.Is.Anything)).Return(true);
            player.Stub(p => p.HasBall).Return(true);
            player.Stub(p => p.CanReachBall).Return(true);
            player.Stub(p => p.VisiblePlayersFromOwnTeam).Return(other.AsEnumerable());
            player.Expect(p => p.PassBall(Arg<IPlayer>.Is.Anything));

            state.Execute();

            player.VerifyAllExpectations();
        }

        [Test]
        public void ShouldConsiderPassingBallOnlyToPlayersHeSees()
        {
            IPlayer other1 = MockRepository.GenerateStub<IPlayer>();
            IPlayer other2 = MockRepository.GenerateStub<IPlayer>();
            IEnumerable<IPlayer> visiblePlayers = TestHelper.Enumerable(other1, other2);

            player.Stub(p => p.VisiblePlayersFromOwnTeam).Return(visiblePlayers);
            player.Stub(p => p.HasBall).Return(true);
            player.Stub(p => p.CanReachBall).Return(true);
            random.Stub(r => r.ShouldPassBall(Arg<float>.Is.Anything)).Return(true);
            random.Expect(r => r.PlayerToPassBallTo(Arg.Is(visiblePlayers), Arg<float[]>.Is.Anything)).Return(other1);

            state.Execute();

            random.VerifyAllExpectations();
        }

        [Test]
        public void ShouldPassToCloserPlayerWithHigherChances()
        {
            IPlayer other1 = MockRepository.GenerateStub<IPlayer>();
            IPlayer other2 = MockRepository.GenerateStub<IPlayer>();
            IEnumerable<IPlayer> visiblePlayers = TestHelper.Enumerable(other1, other2);

            player.Stub(p => p.DistanceTo(other1)).Return(20);
            player.Stub(p => p.DistanceTo(other2)).Return(10);
            other1.Stub(p => p.DistanceToOpponentGoal).Return(10);
            other2.Stub(p => p.DistanceToOpponentGoal).Return(20);

            player.Stub(p => p.VisiblePlayersFromOwnTeam).Return(visiblePlayers);
            player.Stub(p => p.HasBall).Return(true);
            player.Stub(p => p.CanReachBall).Return(true);

            random.Stub(r => r.ShouldPassBall(Arg<float>.Is.Anything)).Return(true);
            random.Expect(r => r.PlayerToPassBallTo(Arg.Is(visiblePlayers), Arg<float[]>.Matches(a => a[0] < a[1]))).Return(other1);

            state.Execute();

            random.VerifyAllExpectations();
        }

        [Test]
        public void ShouldPassToPlayerCloserToOpponentGoalWithHigherChances()
        {
            IPlayer other1 = MockRepository.GenerateStub<IPlayer>();
            IPlayer other2 = MockRepository.GenerateStub<IPlayer>();
            IEnumerable<IPlayer> visiblePlayers = TestHelper.Enumerable(other1, other2);

            other1.Stub(p => p.DistanceToOpponentGoal).Return(20);
            other2.Stub(p => p.DistanceToOpponentGoal).Return(10);

            player.Stub(p => p.VisiblePlayersFromOwnTeam).Return(visiblePlayers);
            player.Stub(p => p.HasBall).Return(true);
            player.Stub(p => p.CanReachBall).Return(true);

            random.Stub(r => r.ShouldPassBall(Arg<float>.Is.Anything)).Return(true);
            random.Expect(r => r.PlayerToPassBallTo(Arg.Is(visiblePlayers), Arg<float[]>.Matches(a => a[0] < a[1]))).Return(other1);

            state.Execute();

            random.VerifyAllExpectations();
        }

        [Test]
        public void ShouldShoot()
        {
            random.Stub(r => r.ShouldShoot(Arg<float>.Is.Anything)).Return(true);

            player.Stub(p => p.HasBall).Return(true);
            player.Stub(p => p.CanReachBall).Return(true);
            player.Expect(p => p.Shoot());

            state.Execute();

            player.VerifyAllExpectations();
        }

        [Test]
        public void ShouldNotShootWhenFarFromOpponentGoal()
        {
            random.Expect(r => r.ShouldShoot(0)).Return(false);

            player.Stub(p => p.HasBall).Return(true);
            player.Stub(p => p.CanReachBall).Return(true);
            player.Stub(p => p.VisiblePlayersFromOwnTeam).Return(Enumerable.Empty<IPlayer>());
            player.Stub(p => p.DistanceToOpponentGoal).Return(100);

            state.Execute();

            random.VerifyAllExpectations();
        }

        [Test]
        public void ShouldRatherShootWhenCloseToOpponentGoal()
        {
            random.Stub(r => r.ShouldShoot(Arg<float>.Matches(c => c > 0))).Return(true);

            player.Stub(p => p.HasBall).Return(true);
            player.Stub(p => p.CanReachBall).Return(true);
            player.Stub(p => p.DistanceToOpponentGoal).Return(10);
            player.Stub(p => p.IsFacingOpponentGoal).Return(true);
            player.Expect(p => p.Shoot());

            state.Execute();

            player.VerifyAllExpectations();
        }

        [Test]
        public void ShootingChanceShouldBeHigherWhenCloserToOpponentGoal()
        {
            IPlayer player1 = MockRepository.GenerateStub<IPlayer>();
            IPlayer player2 = MockRepository.GenerateStub<IPlayer>();
            player1.Stub(p => p.HasBall).Return(true);
            player2.Stub(p => p.HasBall).Return(true);
            player1.Stub(p => p.CanReachBall).Return(true);
            player2.Stub(p => p.CanReachBall).Return(true);
            player1.Stub(p => p.DistanceToOpponentGoal).Return(20);
            player1.Stub(p => p.DistanceToOpponentGoal).Return(10);
            player1.Stub(p => p.IsFacingOpponentGoal).Return(true);
            player2.Stub(p => p.IsFacingOpponentGoal).Return(true);

            ControlBallState state1 = new ControlBallState(player1, random);
            ControlBallState state2 = new ControlBallState(player2, random);

            List<float> chances = new List<float>();
            random.Expect(r => r.ShouldShoot(Arg<float>.Is.Anything)).Repeat.Twice()
                .WhenCalled(o => chances.Add((float)o.Arguments[0])).Return(true);

            state1.Execute();
            state2.Execute();

            random.VerifyAllExpectations();
            Assert.Greater(chances[1], chances[0]);
        }

        [Test]
        public void ShouldNotShootWhenNotFacingOpponentGoal()
        {
            random.Expect(r => r.ShouldShoot(0)).Return(false);

            player.Stub(p => p.HasBall).Return(true);
            player.Stub(p => p.CanReachBall).Return(true);
            player.Stub(p => p.VisiblePlayersFromOwnTeam).Return(Enumerable.Empty<IPlayer>());
            player.Stub(p => p.DistanceToOpponentGoal).Return(20);
            player.Stub(p => p.IsFacingOpponentGoal).Return(false);

            state.Execute();

            random.VerifyAllExpectations();
        }
    }
}
